#include <utility>
#include <iostream>
#include "Tensor.h"

Tensor Tensor::add(const Tensor& other) const {
	return Tensor((this->core).add(other.core));
}

Tensor Tensor::abs() const {
	return Tensor(this->core.abs());
}

Tensor Tensor::sub(const Tensor& other) const {
	return Tensor((this->core).sub(other.core));
}

Tensor Tensor::sum() const {
	return Tensor(this->core.sum());
}

Tensor Tensor::div(const Tensor& other) const {
	return Tensor((this->core).div(other.core));
}

Tensor Tensor::mul(const Tensor& other) const {
	return Tensor((this->core).mul(other.core));
}


Tensor Tensor::add(const Scalar& other) const {
	return Tensor((this->core).add(other.core));
}

Tensor Tensor::sub(const Scalar& other) const {
	return Tensor((this->core).sub(other.core));
}

int64_t Tensor::dim() const noexcept(false) {
	return this->core.dim();
}

Tensor Tensor::div(const Scalar& other) const {
	return Tensor((this->core).div(other.core));
}

Tensor Tensor::div_(const Scalar& other) {
	return Tensor(this->core.div_(other));
}

Tensor Tensor::mul(const Scalar& other) const {
	return Tensor((this->core).mul(other.core));
}

Tensor Tensor::pow(const Scalar& exponent) const {
	return Tensor(this->core.pow(exponent));
}

Tensor Tensor::log() const {
	return Tensor(this->core.log());
}

Tensor Tensor::sum(int64_t dim) const {
	return this->core.sum(dim);
}

Tensor Tensor::sum(const std::vector<int64_t>& dim) const {
	return this->core.sum(dim);
}

Tensor::Tensor(torch::Tensor core) : core(std::move(core)) {
}

Tensor::Tensor() {
}

void Tensor::backward_() {
	core.backward();
}

Tensor Tensor::reshape(const std::vector<int64_t>& size) {
	return Tensor(this->core.reshape(size));
}

Tensor Tensor::view(const std::vector<int64_t>& size) {
	return Tensor(this->core.view(size));
}

Tensor Tensor::grad() const {
	return Tensor(this->core.grad());
}

Tensor Tensor::mean() const {
	return Tensor(this->core.mean());
}

Tensor Tensor::normal_(double weight, double std) {
	return Tensor(core.normal_(weight, std));
}

Tensor Tensor::zero_() {
	return Tensor(core.zero_());
}

Tensor Tensor::squeeze() const {
	return Tensor(core.squeeze());
}

Tensor Tensor::squeeze(int64_t dim) {
	return Tensor(core.squeeze(dim));
}

Tensor Tensor::squeeze_() {
	return Tensor(core.squeeze_());
}

Tensor Tensor::squeeze_(int64_t dim) {
	return Tensor(core.squeeze_(dim));
}

Tensor Tensor::unsqueeze(int64_t dim) const {
	return Tensor(core.unsqueeze(dim));
}

Tensor Tensor::unsqueeze_(int64_t dim) {
	return Tensor(core.unsqueeze(dim));
}

std::vector<int64_t> Tensor::sizes() const {
	c10::IntArrayRef sizes = core.sizes();
	return sizes.vec();
}

int Tensor::size(int dim) const {
	return this->core.size(dim);
}


Scalar Tensor::item() const {
	return Scalar(core.item());
}

Tensor Tensor::cuda() const {
	return core.cuda();
}

Tensor Tensor::cpu() const {
	return core.cpu();
}

Tensor Tensor::hip() const {
	return core.hip();
}

Tensor Tensor::to(Device dev) {
	return core.to(dev);
}

Tensor Tensor::to(Dtype dt) {
	return core.to(dt.core);
}


Tensor Tensor::add(double value, double alpha) {
	c10::Scalar other = c10::Scalar(value);
	c10::Scalar alp = c10::Scalar(alpha);
	return Tensor(this->core.add(other, alp));
}

Tensor Tensor::add_(double value, double alpha) {
	c10::Scalar other = c10::Scalar(value);
	c10::Scalar alp = c10::Scalar(alpha);
	return Tensor(this->core.add_(other, alp));
}

Tensor Tensor::index_add_(int64_t dim, const Tensor& index, const Tensor& source) {
	return Tensor(this->core.index_add_(dim, index, source));
}

Tensor Tensor::index_put_(const std::vector<Tensor>& indices, const Tensor& values, bool accumulate) _EXCEPT {
	std::vector<at::Tensor> v;
	v.reserve(indices.size());
	for (const auto& tensor : indices) {
		v.push_back(tensor.core);
	}
	at::TensorList tensorlist = c10::ArrayRef<at::Tensor>(v);
	return Tensor(this->core.index_put_(tensorlist, values.core, accumulate));
}

Tensor Tensor::index_put_(const Tensor& index, const Tensor& values, bool accumulate) {
	std::vector<at::Tensor> v;
	v.push_back(index.core);
	at::TensorList tensorlist = c10::ArrayRef<at::Tensor>(v);
	return Tensor(this->core.index_put_(tensorlist, values.core, accumulate));
}

Tensor Tensor::index_select(int64_t dim, const Tensor& index) _EXCEPT {
	return Tensor(this->core.index_select(dim, index.core));
}

Tensor Tensor::masked_select(const Tensor& mask) const noexcept(false) {
	return Tensor(this->core.masked_select(mask));
}

Tensor Tensor::narrow_copy(int64_t dim, int64_t start, int64_t length) const {
	return Tensor(this->core.narrow_copy(dim, start, length));
}

Tensor Tensor::narrow(int64_t dim, int64_t start, int64_t length) const {
	return Tensor(this->core.narrow(dim, start, length));
}

Tensor Tensor::to_dense() const {
	return Tensor(this->core.to_dense());
}

Tensor Tensor::select(int64_t dim, int64_t index) const {
	return Tensor(this->core.select(dim, index));
}

Tensor Tensor::mul(long other) const {
	c10::Scalar _other = c10::Scalar(other);
	return Tensor(this->core.mul(_other));
}

Tensor Tensor::flatten(int64_t start_dim, int64_t end_dim) const {
	return Tensor(this->core.flatten(start_dim, end_dim));
}

Tensor Tensor::nonzero() const {
	return Tensor(this->core.nonzero());
}

Tensor Tensor::get(const Scalar& index) const noexcept(false) {
	return this->core[index];
}

Tensor Tensor::get(const Tensor& index) const noexcept(false) {
	return this->core[index];
}

Tensor Tensor::get(const int64_t& index) const noexcept(false) {
	return this->core[index];
}

Tensor Tensor::_indices() const {
	return Tensor(this->core._indices());
}


Tensor Tensor::_values() const {
	return Tensor(this->core._values());
}

Tensor Tensor::indices() const {
	return Tensor(this->core.indices());
}

Tensor Tensor::values() const {
	return Tensor(this->core.values());
}

Tensor Tensor::coalesce() const {
	return Tensor(this->core.coalesce());
}

Tensor Tensor::t() const {
	return Tensor(this->core.t());
}

Tensor Tensor::clone() const {
	return Tensor(this->core.clone());
}


Tensor Tensor::max() const {
	return Tensor(this->core.max());
}

TensorOptions Tensor::options() const {
	return {this->core.options()};
}

Tensor Tensor::mm(const Tensor &other) const {
    return Tensor((this->core).mm(other.core));
}

